/*
  ECMFS: Edge Core Master Filesystem - A Distributed Encrypted Filesystem
                                       in Userspace based on FUSE.
  Author    : Harsha M.
  Banner Id : A20250275
  Course    : CS549, Fall 2014, Illinois Institute of Technology, Chicago
*/

#define FUSE_USE_VERSION 26

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef linux
/* For pread()/pwrite()/utimensat() */
#define _XOPEN_SOURCE 700
#endif

#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <sys/time.h>
#ifdef HAVE_SETXATTR
#include <sys/xattr.h>
#endif
#include <getopt.h>
#include <stdlib.h>
#include "configfile.h"
#include "ssl.h"
#include "ecmfs_api.h"
#include "list.h"
#define DEBUG 0

#if DEBUG
#define DEBUG_LOG(fmt, args...) \
    printf("\n[%s][%d] " fmt "\n", __FUNCTION__, __LINE__ , args);
#else
#define DEBUG_LOG(fmt, args...) 
#endif


/* local path for .ecmfs directory 
 * individual profile are copied in ~/.ecmfs_path/<login>/profile*/


/* locally mounted path $ecmfs_path/<login>/mnt */
char *local_path;

/* remote server ip */
char *server_ip;
int server_port;

//maximum length for a server address
#define MAXADDR 100
#define MAXPUBKEY 2048
#define OK 0
#define ERROR -1
#define MAXUNAME      100
#define MAXPASSWD     100
#define MAXPRIVATEKEY 2048


/* list for storing multiple servers */
list_head head;
list_tail *tail;
//structure for holding server data
//will be kept in a list for a list 
//of Master Servers
typedef struct _server_data {
    COMMCTX g_ctx;
    COMMCTX* ctx;
    SOCK_CTX g_mctx;
    /*pthread_mutex_t ctx_mutex;
    list_head req_head;
    list_tail *req_tail;
    pthread_mutex_t req_mutex;
    sem_t req_sem; 
    unsigned int sequence;
    pthread_mutex_t sequence_mutex; */
} server_data;

extern config_file_t *cfile;
static int ecmfs_getattr(const char *path, struct stat *stbuf)
{
    int res;

    DEBUG_LOG("path=%s, stbuf=%p", path, stbuf);
 
    res = lstat(path, stbuf);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_access(const char *path, int mask)
{
    int res;

    DEBUG_LOG("path=%s, mask=0x%x", path, mask);

    res = access(path, mask);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_readlink(const char *path, char *buf, size_t size)
{
    int res;

    DEBUG_LOG("path=%s, buf=%s size=%d", path, buf, size);

    res = readlink(path, buf, size - 1);
    if (res == -1)
        return -errno;

    buf[res] = '\0';
    return 0;
}


static int ecmfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
               off_t offset, struct fuse_file_info *fi)
{
    DIR *dp;
    struct dirent *de;

    (void) offset;
    (void) fi;

    DEBUG_LOG("path=%s, buf=%p filler=%p offset=%d, fi=%p",
            path, buf, filler, (int) offset, fi);

    dp = opendir(path);
    if (dp == NULL)
        return -errno;

    while ((de = readdir(dp)) != NULL) {
        struct stat st;
        memset(&st, 0, sizeof(st));
        st.st_ino = de->d_ino;
        st.st_mode = de->d_type << 12;
        if (filler(buf, de->d_name, &st, 0))
            break;
    }

    closedir(dp);
    return 0;
}

static int ecmfs_mknod(const char *path, mode_t mode, dev_t rdev)
{
    int res;

    /* On Linux this could just be 'mknod(path, mode, rdev)' but this
       is more portable */

    DEBUG_LOG("path=%s, mode=%d, rdev=%d", path, mode, (int) rdev);

    if (S_ISREG(mode)) {
        res = open(path, O_CREAT | O_EXCL | O_WRONLY, mode);
        if (res >= 0)
            res = close(res);
    } else if (S_ISFIFO(mode))
        res = mkfifo(path, mode);
    else
        res = mknod(path, mode, rdev);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_mkdir(const char *path, mode_t mode)
{
    int res;

    DEBUG_LOG("path=%s mode=%d", path, mode);
    res = mkdir(path, mode);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_unlink(const char *path)
{
    int res;

    DEBUG_LOG("path=%s", path);
    res = unlink(path);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_rmdir(const char *path)
{
    int res;

    DEBUG_LOG("path=%s", path);
    res = rmdir(path);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_symlink(const char *from, const char *to)
{
    int res;

    DEBUG_LOG("from=%s to=%s", from,to);
    res = symlink(from, to);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_rename(const char *from, const char *to)
{
    int res;

    DEBUG_LOG("from=%s to=%s", from, to);
    res = rename(from, to);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_link(const char *from, const char *to)
{
    int res;

    DEBUG_LOG("from=%s to=%s", from, to);
    res = link(from, to);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_chmod(const char *path, mode_t mode)
{
    int res;

    DEBUG_LOG("path=%s, mode=%d", path, mode);
    res = chmod(path, mode);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_chown(const char *path, uid_t uid, gid_t gid)
{
    int res;

    DEBUG_LOG("path=%s, uid=%d, gid=%d", path, uid, gid);
    res = lchown(path, uid, gid);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_truncate(const char *path, off_t size)
{
    int res;

    DEBUG_LOG("path=%s, size=%d", path, (int) size);
    res = truncate(path, size);
    if (res == -1)
        return -errno;

    return 0;
}

#ifdef HAVE_UTIMENSAT
static int ecmfs_utimens(const char *path, const struct timespec ts[2])
{
    int res;

    DEBUG_LOG("path=%s", path);
    /* don't use utime/utimes since they follow symlinks */
    res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
    if (res == -1)
        return -errno;

    return 0;
}
#endif

static int ecmfs_open(const char *path, struct fuse_file_info *fi)
{
    int res;

    DEBUG_LOG("path=%s", path);
    res = open(path, fi->flags);
    if (res == -1)
        return -errno;

    close(res);
    return 0;
}

static int ecmfs_read(const char *path, char *buf, size_t size, off_t offset,
            struct fuse_file_info *fi)
{
    int fd;
    int res;

    DEBUG_LOG("path=%s", path);
    (void) fi;
    fd = open(path, O_RDONLY);
    if (fd == -1)
        return -errno;

    res = pread(fd, buf, size, offset);
    if (res == -1)
        res = -errno;

    close(fd);
    return res;
}

static int ecmfs_write(const char *path, const char *buf, size_t size,
             off_t offset, struct fuse_file_info *fi)
{
    int fd;
    int res;

    DEBUG_LOG("path=%s", path);
    (void) fi;
    fd = open(path, O_WRONLY);
    if (fd == -1)
        return -errno;

    res = pwrite(fd, buf, size, offset);
    if (res == -1)
        res = -errno;

    close(fd);
    return res;
}

static int ecmfs_statfs(const char *path, struct statvfs *stbuf)
{
    int res;

    res = statvfs(path, stbuf);
    DEBUG_LOG("path=%s", path);
    if (res == -1)
        return -errno;

    return 0;
}

static int ecmfs_release(const char *path, struct fuse_file_info *fi)
{
    /* Just a stub.     This method is optional and can safely be left
       unimplemented */
    DEBUG_LOG("path=%s", path);
    (void) path;
    (void) fi;
    return 0;
}

static int ecmfs_fsync(const char *path, int isdatasync,
             struct fuse_file_info *fi)
{
    /* Just a stub.     This method is optional and can safely be left
       unimplemented */
    DEBUG_LOG("path=%s", path);
    (void) path;
    (void) isdatasync;
    (void) fi;
    return 0;
}

#ifdef HAVE_POSIX_FALLOCATE
static int ecmfs_fallocate(const char *path, int mode,
            off_t offset, off_t length, struct fuse_file_info *fi)
{
    int fd;
    int res;

    DEBUG_LOG("path=%s mode=%d, offset=%d length=%d fi=%p",
            path, mode, offset, lengthm, fi);
    (void) fi;

    if (mode)
        return -EOPNOTSUPP;

    fd = open(path, O_WRONLY);
    if (fd == -1)
        return -errno;

    res = -posix_fallocate(fd, offset, length);

    close(fd);
    return res;
}
#endif

#ifdef HAVE_SETXATTR
/* xattr operations are optional and can safely be left unimplemented */
static int ecmfs_setxattr(const char *path, const char *name,
                          const char *value,
            size_t size, int flags)
{

    int res = lsetxattr(path, name, value, size, flags);
    DEBUG_LOG("path=%s name=%s value=%s, size=%d flags=%d",
            path, name, value, size, flags);
    if (res == -1)
        return -errno;
    return 0;
}

static int ecmfs_getxattr(const char *path, const char *name,
                          char *value,size_t size)
{

    int res = lgetxattr(path, name, value, size);
    DEBUG_LOG("path=%s name=%s value=%s size=%d",
                path, name, value, size);
    if (res == -1)
        return -errno;
    return res;
}

static int ecmfs_listxattr(const char *path, char *list, size_t size)
{

    int res = llistxattr(path, list, size);
    DEBUG_LOG("path=%s list=%s size=%d", path, list, size);
    if (res == -1)
        return -errno;
    return res;
}

static int ecmfs_removexattr(const char *path, const char *name)
{

    int res = lremovexattr(path, name);
    DEBUG_LOG("path=%s name=%s", path, name);
    if (res == -1)
        return -errno;
    return 0;
}
#endif /* HAVE_SETXATTR */

static struct fuse_operations ecmfs_oper = {
    .getattr    = ecmfs_getattr,
    .access        = ecmfs_access,
    .readlink    = ecmfs_readlink,
    .readdir    = ecmfs_readdir,
    .mknod        = ecmfs_mknod,
    .mkdir        = ecmfs_mkdir,
    .symlink    = ecmfs_symlink,
    .unlink        = ecmfs_unlink,
    .rmdir        = ecmfs_rmdir,
    .rename        = ecmfs_rename,
    .link        = ecmfs_link,
    .chmod        = ecmfs_chmod,
    .chown        = ecmfs_chown,
    .truncate    = ecmfs_truncate,
#ifdef HAVE_UTIMENSAT
    .utimens    = ecmfs_utimens,
#endif
    .open        = ecmfs_open,
    .read        = ecmfs_read,
    .write        = ecmfs_write,
    .statfs        = ecmfs_statfs,
    .release    = ecmfs_release,
    .fsync        = ecmfs_fsync,
#ifdef HAVE_POSIX_FALLOCATE
    .fallocate    = ecmfs_fallocate,
#endif
#ifdef HAVE_SETXATTR
    .setxattr    = ecmfs_setxattr,
    .getxattr    = ecmfs_getxattr,
    .listxattr    = ecmfs_listxattr,
    .removexattr    = ecmfs_removexattr,
#endif
};

#define MAX_FARGC 20
int fargc=0;
char* fargv[MAX_FARGC];

int add_argument_for_fuse(char* new_arg)
{
    char* new_alloc;

    if (new_arg == NULL) {
        return -1;
    }
    
    if ((fargc+1) >= MAX_FARGC) {
        return -1;
    }

    new_alloc=malloc(strlen(new_arg)+1);
    if (new_alloc == NULL) {
        return -1;
    }

    strcpy(new_alloc, new_arg);

    fargv[fargc++]=new_alloc;

    return 0;
}


    
session_aes_key_t sess;
int start_mount_session(int argc,char **argv) {
    int ret;
    int i;
     
    /* Make session request */
    ret = send_ecmfs_request(CLIENT_OP_MOUNT, &sess);
    if (ret <= 0) {
        fprintf(stderr, "No session keys");
        return 0;
    }
    
    add_argument_for_fuse(argv[0]);
    add_argument_for_fuse("-s");
    //add_argument_for_fuse("-d");
    add_argument_for_fuse(cfile->mount_path);
    printf("Arguments for FUSE: ");
    for (i=0; i<fargc; i++) {
        printf("%s ", fargv[i]);
    }    
    ret = fuse_main(fargc, fargv, &ecmfs_oper, NULL);
    return ret;
}


